package co.fitstart.weapp.api.service.impl;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.demo2do.core.entity.exception.BusinessException;
import com.demo2do.core.persistence.GenericDaoSupport;
import com.demo2do.core.persistence.annotation.Dao;

import co.fitstart.entity.Constant;
import co.fitstart.entity.coach.CoachRestCalendar;
import co.fitstart.entity.coach.CoachRestType;
import co.fitstart.entity.coach.CoachStudentRelation;
import co.fitstart.entity.course.round.CourseRound;
import co.fitstart.entity.course.schedule.CourseSchedule;
import co.fitstart.entity.course.schedule.CourseScheduleCancelRecord;
import co.fitstart.entity.course.schedule.ScheduleSmsNotice;
import co.fitstart.entity.course.schedule.ScheduleStatus;
import co.fitstart.entity.courseware.fitnessgroup.BatchCourseware;
import co.fitstart.entity.courseware.fitnessgroup.BatchCoursewareType;
import co.fitstart.entity.order.OrderStatus;
import co.fitstart.entity.principal.Principal;
import co.fitstart.entity.principal.PrincipalMessage;
import co.fitstart.entity.site.RoomScheduleRecord;
import co.fitstart.entity.site.RoomType;
import co.fitstart.entity.site.Site;
import co.fitstart.entity.user.Student;
import co.fitstart.entity.utils.DateFormatUtils;
import co.fitstart.entity.utils.DateUtils;
import co.fitstart.weapp.api.dao.CoachDao;
import co.fitstart.weapp.api.dao.CourseScheduleDao;
import co.fitstart.weapp.api.dao.CoursewareDao;
import co.fitstart.weapp.api.dao.SiteDao;
import co.fitstart.weapp.api.handler.SmsSender;
import co.fitstart.weapp.api.handler.TencentHandler;
import co.fitstart.weapp.api.service.CourseAppointService;
import co.fitstart.weapp.api.utils.CourseAppointmentUtils;

/**
 * 
 * @author haiyan_xiao
 *
 */
@Service("courseAppointService")
public class CourseAppointServiceImpl implements CourseAppointService {

    @Dao
    private SiteDao siteDao;

    @Dao
    private CourseScheduleDao courseScheduleDao;

    @Dao
    private CoachDao coachDao;

    @Dao
    private CoursewareDao coursewareDao;

    @Autowired
    private GenericDaoSupport genericDaoSupport;

    @Autowired
    private SmsSender smsSender;

    @Autowired
    private TencentHandler tencentHandler;

    /*
     * (non-Javadoc)
     * @see co.fitstart.weapp.api.service.CourseAppointService#listAvailableDay(co.fitstart.entity.course.round.CourseRound)
     */
    public List<String> listAvailableDay(CourseRound courseRound) {
        List<String> days = new ArrayList<>();
        
        Date today = DateUtils.parseDate(DateFormatUtils.format(new Date(), "yyyy-MM-dd") + " 00:00:00", "yyyy-MM-dd HH:mm:ss");
        Date lastDay = DateUtils.addDays(today, 14);
        
        if(courseRound.getEffectiveEnd() != null) {
            String appointStartHour = siteDao.getEarliestAppointStartHour();
            Date effectiveEnd;
            
            if(org.apache.commons.lang3.time.DateUtils.addHours(courseRound.getEffectiveEnd(), -1).before(DateUtils.parseDate(DateFormatUtils.format(courseRound.getEffectiveEnd(), "yyyy-MM-dd ") + appointStartHour + ":00", "yyyy-MM-dd HH:mm:ss"))) {
                effectiveEnd = DateUtils.parseDate(DateFormatUtils.format(DateUtils.addDays(courseRound.getEffectiveEnd(), -1), "yyyy-MM-dd") + " 23:59:59", "yyyy-MM-dd HH:mm:ss");
            } else {
                effectiveEnd = courseRound.getEffectiveEnd();
            }
            
            if(effectiveEnd.before(lastDay)) {
                lastDay = effectiveEnd;
            }
        }
        
        for(Date day = today; day.before(lastDay); day = DateUtils.addDays(day, 1)) {
            days.add(DateFormatUtils.format(day, "yyyy-MM-dd"));
        }
        
        return days;
    }

    /*
     * (non-Javadoc)
     * @see co.fitstart.weapp.api.service.CourseAppointService#listAvailableSite(co.fitstart.entity.site.Site)
     */
    public Map<String, Map<String, List<Site>>> listAvailableSite(Site site) {
        if(site.getArea() != null && site.getArea().getCityLevel() != null && site.getArea().getAreaLevel() != null) {
            List<Site> sites = siteDao.listAvailableSite(site.getArea().getCityLevel(), site.getArea().getAreaLevel());
            
            if(sites.isEmpty()) {
                return null;
            } else {
                Map<String, Map<String, List<Site>>> cityMap = new HashMap<>();
                Map<String, List<Site>> districtMap = new HashMap<>();
                String city = sites.get(0).getCity();
                String district = sites.get(0).getDistrict();
                List<Site>  districtSites = new ArrayList<>();
                
                for(Site eachSite : sites) {
                    if(eachSite.getCity().equals(city)) {
                        if(!eachSite.getDistrict().equals(district)) {
                            districtMap.put(district, districtSites);
                            
                            district = eachSite.getDistrict();
                            districtSites = new ArrayList<>();
                        }
                    } else {
                        districtMap.put(district, districtSites);
                        cityMap.put(city, districtMap);
                        
                        districtMap = new HashMap<>();
                        city = eachSite.getCity();
                        district = eachSite.getDistrict();
                        districtSites = new ArrayList<>();
                    }
                    
                    districtSites.add(eachSite);
                }
                
                districtMap.put(district, districtSites);
                cityMap.put(city, districtMap);
                
                return cityMap;
            }
        } else if(site.isDisabled()) {
            return null;
        } else {
            Map<String, Map<String, List<Site>>> cityMap = new HashMap<>();
            Map<String, List<Site>> districtMap = new HashMap<>();
            
            districtMap.put(site.getDistrict(), Arrays.asList(new Site[]{site}));
            cityMap.put(site.getCity(), districtMap);
            
            return cityMap;
        }
    }

    /*
     * (non-Javadoc)
     * @see co.fitstart.weapp.api.service.CourseAppointService#validate(co.fitstart.entity.user.Student, co.fitstart.entity.course.round.CourseRound)
     */
    public void validate(Student student, CourseRound courseRound) {
        // check course type
        if(!courseRound.getCourseType().supportAppointment()) {
            throw new BusinessException("该课程不支持预约");
        }

        // check courseRound matches student or not
        if(student == null || !courseRound.matches(student)) {
            throw new BusinessException("学员课程数据有误");
        }
        
        if(!courseRound.getCourseStatus().isPrepare() && !courseRound.getCourseStatus().isReady()) {
            throw new BusinessException(courseRound.getCourseStatus().getDescription() + "课程不可预约");
        } else if(courseRound.getCourseStatus().isPrepare() && courseRound.getCoach() == null) {
            throw new BusinessException("暂未分配主教练，不可预约");
        }
    }

    /*
     * (non-Javadoc)
     * @see co.fitstart.weapp.api.service.CourseAppointService#validate(co.fitstart.entity.user.Student, co.fitstart.entity.course.round.CourseRound, co.fitstart.entity.course.schedule.CourseSchedule, java.util.Date)
     */
    public void validate(Student student, CourseRound courseRound, CourseSchedule courseSchedule, Date occurDate) {
        this.validate(student, courseRound);
        
        if(courseSchedule != null) {
            //check courseSchedule matchs courseRound or not
            if(courseSchedule.getCourseRound() != courseRound) {
                throw new BusinessException("学员课程数据有误");
            }
            
            if(!courseSchedule.getScheduleStatus().isReady() || !DateUtils.parseDate(DateFormatUtils.format(courseSchedule.getOccurDate(), "yyyy-MM-dd ") + courseSchedule.getOccurHour(), "yyyy-MM-dd HH:mm").after(new Date())) {
                throw new BusinessException("不可换课");
            }
        }
        
        if(!DateUtils.isSameDay(occurDate, new Date()) && occurDate.before(new Date())) {
            throw new BusinessException("所选日期不可预约");
        } else if(courseRound.getEffectiveEnd() != null && occurDate.after(courseRound.getEffectiveEnd())) {
            throw new BusinessException("所选日期课程已过期，不可预约");
        }
    }

    /**
     * 
     * @param student
     * @param courseRound
     * @param courseSchedule
     * @param site
     * @param occurDate
     * @param occurHour
     */
    public void validate(Student student, CourseRound courseRound, CourseSchedule courseSchedule, Site site, Date occurDate, String occurHour) {
        this.validate(student, courseRound, courseSchedule, occurDate);
        
        this.validateCourseTime(courseRound, site, occurDate, occurHour);
        
        this.validatSite(courseRound, site);
    }

    /*
     * 
     */
    public Site getDefaultSite(CourseRound courseRound, Site selectedSite) {
        if(courseRound.getCourseType().isYear()) {
            if(selectedSite == null) {
                if(!courseRound.getSite().isDisabled()) {
                    return courseRound.getSite();
                } else {
                    throw new BusinessException("无可预约门店");
                }
            } else {
                if(selectedSite == courseRound.getSite()) {
                    return selectedSite;
                } else {
                    throw new BusinessException("所选门店不可预约");
                }
            }
        } else {
            if(selectedSite == null) {
                if(!courseRound.getSite().isDisabled()) {
                    return courseRound.getSite();
                } else if(courseRound.getSite().getArea() != null && courseRound.getSite().getArea().getCityLevel() != null && courseRound.getSite().getArea().getAreaLevel() != null) {
                    List<Site> sites = siteDao.listAvailableSite(courseRound.getSite().getArea().getCityLevel(), courseRound.getSite().getArea().getAreaLevel());
                    
                    if(sites.isEmpty()) {
                        throw new BusinessException("无可预约门店");
                    } else {
                        return sites.get(0);
                    }
                } else {
                    throw new BusinessException("无可预约门店");
                }
            } else {
                if(courseRound.getSite() == selectedSite) {
                    return selectedSite;
                } else if(courseRound.getSite().getArea() != null && courseRound.getSite().getArea().getCityLevel() != null && courseRound.getSite().getArea().getAreaLevel() != null) {
                    List<Site> sites = siteDao.listAvailableSite(courseRound.getSite().getArea().getCityLevel(), courseRound.getSite().getArea().getAreaLevel());
                    
                    if(sites.contains(selectedSite)) {
                        return selectedSite;
                    } else {
                        throw new BusinessException("所选门店不可预约");
                    }
                } else {
                    throw new BusinessException("所选门店不可预约");
                }
            }
        }
    }

    /*
     * (non-Javadoc)
     * @see co.fitstart.weapp.api.service.CourseAppointService#listRemain(co.fitstart.entity.course.round.CourseRound, java.util.Date, co.fitstart.entity.site.Site)
     */
    public List<Map<String, Object>> listRemain(CourseRound courseRound, Date day, Site site) {
        List<Map<String, Object>> remains = new ArrayList<>();
        
        List<String> appointTimes = this.getAppointTimes(courseRound, site, day);
        if(appointTimes.size() > 0) {
            boolean experience = courseRound.getCourse().isExperience();
            
            //获取剩余教练
            Map<String, Integer> remainCoaches = this.remainCoach(site, day, appointTimes, experience);
            
            if(!remainCoaches.isEmpty()) {
                //剩余私人区域
                Map<String, Integer> remainRooms = this.remainRoom(RoomType.ROOM, site, day, appointTimes, experience);
                //剩余公共区域
                Map<String,Integer> remainCommons = this.remainRoom(RoomType.COMMON, site, day, appointTimes, experience);
                
                for(String appointTime : appointTimes) {
                    Map<String, Object> map = new HashMap<>();
                    map.put("occurHour", appointTime);
                    map.put("endHour", this.getOccurHour(appointTime, courseRound.getCourse().isExperience() ? 3 : 2));
                    
                    int remainRoom = remainRooms.get(appointTime);
                    int remainCommon = remainCommons.get(appointTime);
                    int remainCoach = remainCoaches.get(appointTime);
                    map.put("roomAvailable", remainRoom > remainCoach ? remainCoach : remainRoom);
                    map.put("commonAvailable", remainCommon > remainCoach ? remainCoach : remainCommon);
                    
                    remains.add(map);
                }
            }
        }
        
        return remains;
    }

    /*
     * (non-Javadoc)
     * @see co.fitstart.weapp.api.service.CourseAppointService#listRemain(co.fitstart.entity.course.round.CourseRound, java.util.Date, co.fitstart.entity.site.Site, co.fitstart.entity.course.schedule.CourseSchedule)
     */
    public List<Map<String, Object>> listRemain(CourseRound courseRound, Date day, Site site, CourseSchedule courseSchedule) {
        List<Map<String, Object>> remains = new ArrayList<>();
        
        List<String> appointTimes = this.getAppointTimes(courseRound, site, day);
        if(appointTimes.size() > 0) {
            boolean experience = courseRound.getCourse().isExperience();
            
            //获取剩余教练
            Map<String, Integer> remainCoaches = this.remainCoach(site, day, appointTimes, experience, courseSchedule);
            
            if(!remainCoaches.isEmpty()) {
              //剩余私人区域
                Map<String, Integer> remainRooms = this.remainRoom(RoomType.ROOM, site, day, appointTimes, experience, courseSchedule);
                //剩余公共区域
                Map<String,Integer> remainCommons = this.remainRoom(RoomType.COMMON, site, day, appointTimes, experience, courseSchedule);
                
                for(String appointTime : appointTimes) {
                    Map<String, Object> map = new HashMap<>();
                    map.put("occurHour", appointTime);
                    map.put("endHour", this.getOccurHour(appointTime, courseRound.getCourse().isExperience() ? 3 : 2));
                    
                    int remainRoom = remainRooms.get(appointTime);
                    int remainCommon = remainCommons.get(appointTime);
                    int remainCoach = remainCoaches.get(appointTime);
                    map.put("roomAvailable", remainRoom > remainCoach ? remainCoach : remainRoom);
                    map.put("commonAvailable", remainCommon > remainCoach ? remainCoach : remainCommon);
                    
                    remains.add(map);
                }
            }
        }
        
        return remains;
    }

    /**
     * 各时间可预约教练数
     * @param site
     * @param day
     * @param appointTimes
     * @return
     */
    private Map<String, Integer> remainCoach(Site site, Date day, List<String> appointTimes, boolean experience) {
        Map<String, Integer> remainCoaches = new HashMap<>();
        
        List<Principal> coaches = this.listCoach(site, day);
        
        if(!coaches.isEmpty()) {
            //各时间上课教练
            Map<String, List<Long>> hourCoachMap = new HashMap<>();
            
            List<Map<String, Object>> hourCoachList = courseScheduleDao.list(day, coaches);
            
            if(!hourCoachList.isEmpty()) {
                String occurHour = (String)hourCoachList.get(0).get("occurHour");
                List<Long> coachIds = new ArrayList<>();
                
                for(Map<String, Object> map : hourCoachList) {
                    if(!occurHour.equals((String)map.get("occurHour"))) {
                        hourCoachMap.put(occurHour, coachIds);
                        occurHour = (String)map.get("occurHour");
                        coachIds = new ArrayList<>();
                    }
                    
                    coachIds.add((Long)map.get("coachId"));
                }
                
                hourCoachMap.put(occurHour, coachIds);
            }
            
            //各时间不可排课教练
            Map<String, List<Long>> hourCoachCalendarMap = new HashMap<>();
            
            List <Map<String, Object>> hourCoachCalendarList = coachDao.listCalendar(day, coaches, CoachRestType.DUTY);
            
            if(!hourCoachCalendarList.isEmpty()) {
                String restHour = (String)hourCoachCalendarList.get(0).get("restHour");
                List<Long> coachIds = new ArrayList<>();
                
                for(Map<String, Object> map : hourCoachCalendarList) {
                    if(!restHour.equals((String)map.get("restHour"))) {
                        hourCoachCalendarMap.put(restHour, coachIds);
                        restHour = (String)map.get("restHour");
                        coachIds = new ArrayList<>();
                    }
                    
                    coachIds.add((Long)map.get("coachId"));
                }
                
                hourCoachCalendarMap.put(restHour, coachIds);
            }
            
            //各时间剩余可用教练数
            int totalCoach = coaches.size();
            for(String appointTime : appointTimes) {
                Date current = DateUtils.parseDate(DateFormatUtils.format(day, "yyyy-MM-dd ") + appointTime + ":00", "yyyy-MM-dd HH:mm:ss");
                String beforeHalfhour = DateFormatUtils.format(org.apache.commons.lang3.time.DateUtils.addMinutes(current, -30), "HH:mm");
                String afterHalfhour = DateFormatUtils.format(org.apache.commons.lang3.time.DateUtils.addMinutes(current, 30), "HH:mm");
                
                Set<Long> unavailableCoachIds = new HashSet<>();
                
                if(hourCoachMap.containsKey(appointTime)) {
                    unavailableCoachIds.addAll(hourCoachMap.get(appointTime));
                }
                if(hourCoachMap.containsKey(beforeHalfhour)) {
                    unavailableCoachIds.addAll(hourCoachMap.get(beforeHalfhour));
                }
                if(hourCoachMap.containsKey(afterHalfhour)) {
                    unavailableCoachIds.addAll(hourCoachMap.get(afterHalfhour));
                }
                
                if(hourCoachCalendarMap.containsKey(appointTime)) {
                    unavailableCoachIds.addAll(hourCoachCalendarMap.get(appointTime));
                }
                if(hourCoachCalendarMap.containsKey(afterHalfhour)) {
                    unavailableCoachIds.addAll(hourCoachCalendarMap.get(afterHalfhour));
                }
                
                if(experience) {
                    String afterHour = DateFormatUtils.format(org.apache.commons.lang3.time.DateUtils.addHours(current, 1), "HH:mm");
                    
                    if(hourCoachMap.containsKey(afterHour)) {
                        unavailableCoachIds.addAll(hourCoachMap.get(afterHour));
                    }
                    
                    if(hourCoachCalendarMap.containsKey(afterHour)) {
                        unavailableCoachIds.addAll(hourCoachCalendarMap.get(afterHour));
                    }
                }
                
                remainCoaches.put(appointTime, totalCoach > unavailableCoachIds.size() ? totalCoach - unavailableCoachIds.size() : 0);
            }
        }
        
        return remainCoaches;
    }

    private Map<String, Integer> remainCoach(Site site, Date day, List<String> appointTimes, boolean experience, CourseSchedule courseSchedule) {
        Map<String, Integer> remainCoaches = new HashMap<>();
        
        List<Principal> coaches = this.listCoach(site, day);
        
        if(!coaches.isEmpty()) {
            //各时间上课教练
            Map<String, List<Long>> hourCoachMap = new HashMap<>();
            
            List<Map<String, Object>> hourCoachList = new ArrayList<>();
            if(DateUtils.isSameDay(day, courseSchedule.getOccurDate())) {
                hourCoachList = courseScheduleDao.list(day, coaches, courseSchedule);
            } else {
                hourCoachList = courseScheduleDao.list(day, coaches);
            }
            
            if(!hourCoachList.isEmpty()) {
                String occurHour = (String)hourCoachList.get(0).get("occurHour");
                List<Long> coachIds = new ArrayList<>();
                
                for(Map<String, Object> map : hourCoachList) {
                    if(!occurHour.equals((String)map.get("occurHour"))) {
                        hourCoachMap.put(occurHour, coachIds);
                        occurHour = (String)map.get("occurHour");
                        coachIds = new ArrayList<>();
                    }
                    
                    coachIds.add((Long)map.get("coachId"));
                }
                
                hourCoachMap.put(occurHour, coachIds);
            }
            
            //各时间不可排课教练
            Map<String, List<Long>> hourCoachCalendarMap = new HashMap<>();
            
            List <Map<String, Object>> hourCoachCalendarList = new ArrayList<>();
            if(courseSchedule.getCourse().isExperience() && DateUtils.isSameDay(day, courseSchedule.getOccurDate())) {
                CoachRestCalendar experienceCalendar = coachDao.loadCoachRestCalendar(courseSchedule.getCoach(), CoachRestType.EXPERIENCE, courseSchedule.getOccurDate(), (Integer.parseInt(courseSchedule.getOccurHour().split(":")[0]) + 1) + ":" + courseSchedule.getOccurHour().split(":")[1]);
                
                if(experienceCalendar != null) {
                    hourCoachCalendarList = coachDao.listCalendar(day, coaches, CoachRestType.DUTY, experienceCalendar);
                } else {
                    hourCoachCalendarList = coachDao.listCalendar(day, coaches, CoachRestType.DUTY);
                }
            } else {
                hourCoachCalendarList = coachDao.listCalendar(day, coaches, CoachRestType.DUTY);
            }
            
            if(!hourCoachCalendarList.isEmpty()) {
                String restHour = (String)hourCoachCalendarList.get(0).get("restHour");
                List<Long> coachIds = new ArrayList<>();
                
                for(Map<String, Object> map : hourCoachCalendarList) {
                    if(!restHour.equals((String)map.get("restHour"))) {
                        hourCoachCalendarMap.put(restHour, coachIds);
                        restHour = (String)map.get("restHour");
                        coachIds = new ArrayList<>();
                    }
                    
                    coachIds.add((Long)map.get("coachId"));
                }
                
                hourCoachCalendarMap.put(restHour, coachIds);
            }
            
            //各时间剩余可用教练数
            int totalCoach = coaches.size();
            for(String appointTime : appointTimes) {
                Date current = DateUtils.parseDate(DateFormatUtils.format(day, "yyyy-MM-dd ") + appointTime + ":00", "yyyy-MM-dd HH:mm:ss");
                String beforeHalfhour = DateFormatUtils.format(org.apache.commons.lang3.time.DateUtils.addMinutes(current, -30), "HH:mm");
                String afterHalfhour = DateFormatUtils.format(org.apache.commons.lang3.time.DateUtils.addMinutes(current, 30), "HH:mm");
                
                Set<Long> unavailableCoachIds = new HashSet<>();
                
                if(hourCoachMap.containsKey(appointTime)) {
                    unavailableCoachIds.addAll(hourCoachMap.get(appointTime));
                }
                if(hourCoachMap.containsKey(beforeHalfhour)) {
                    unavailableCoachIds.addAll(hourCoachMap.get(beforeHalfhour));
                }
                if(hourCoachMap.containsKey(afterHalfhour)) {
                    unavailableCoachIds.addAll(hourCoachMap.get(afterHalfhour));
                }
                
                if(hourCoachCalendarMap.containsKey(appointTime)) {
                    unavailableCoachIds.addAll(hourCoachCalendarMap.get(appointTime));
                }
                if(hourCoachCalendarMap.containsKey(afterHalfhour)) {
                    unavailableCoachIds.addAll(hourCoachCalendarMap.get(afterHalfhour));
                }
                
                if(experience) {
                    String afterHour = DateFormatUtils.format(org.apache.commons.lang3.time.DateUtils.addHours(current, 1), "HH:mm");
                    
                    if(hourCoachMap.containsKey(afterHour)) {
                        unavailableCoachIds.addAll(hourCoachMap.get(afterHour));
                    }
                    
                    if(hourCoachCalendarMap.containsKey(afterHour)) {
                        unavailableCoachIds.addAll(hourCoachCalendarMap.get(afterHour));
                    }
                }
                
                remainCoaches.put(appointTime, totalCoach > unavailableCoachIds.size() ? totalCoach - unavailableCoachIds.size() : 0);
            }
        }
        
        return remainCoaches;
    }

    /**
     * 各时间可用房间数
     * @param roomType 房间类型
     * @param site
     * @param day
     * @param appointTimes
     * @param experience
     * @return
     */
    private Map<String, Integer> remainRoom(RoomType roomType, Site site, Date day, List<String> appointTimes, boolean experience) {
        Map<String, Integer> remainRooms = new HashMap<>();
        
        Map<String, Integer> usingRoomMap = new HashMap<>();
        
        List<Map<String, Object>> usingRoomList = courseScheduleDao.list(roomType, site, day);
        if(!usingRoomList.isEmpty()) {
            for(Map<String, Object> map : usingRoomList) {
                usingRoomMap.put((String)map.get("occurHour"), ((Long)map.get("usingRoom")).intValue());
            }
        }
        
        Map<String, Integer> usingExperienceRoomMap = new HashMap<>();
        
        List<Map<String, Object>> usingExperienceRoomList = courseScheduleDao.listExperience(roomType, site, day);
        if(!usingExperienceRoomList.isEmpty()) {
            for(Map<String, Object> map : usingExperienceRoomList) {
                usingExperienceRoomMap.put((String)map.get("occurHour"), ((Long)map.get("usingRoom")).intValue());
            }
        }
        
        int totalRoom = site.getLimit(roomType);
        for(String appointTime : appointTimes) {
            Date current = DateUtils.parseDate(DateFormatUtils.format(day, "yyyy-MM-dd ") + appointTime + ":00", "yyyy-MM-dd HH:mm:ss");
            String beforeHour = DateFormatUtils.format(org.apache.commons.lang3.time.DateUtils.addHours(current, -1), "HH:mm");
            String beforeHalfhour = DateFormatUtils.format(org.apache.commons.lang3.time.DateUtils.addMinutes(current, -30), "HH:mm");
            String afterHalfhour = DateFormatUtils.format(org.apache.commons.lang3.time.DateUtils.addMinutes(current, 30), "HH:mm");
            
            int firstHalf = 0,secondHalf = 0;
            
            if(usingExperienceRoomMap.containsKey(beforeHour)) {
                firstHalf += usingExperienceRoomMap.get(beforeHour);
            }
            if(usingExperienceRoomMap.containsKey(beforeHalfhour)) {
                secondHalf += usingExperienceRoomMap.get(beforeHalfhour);
            }
            
            if(usingRoomMap.containsKey(beforeHalfhour)) {
                firstHalf += usingRoomMap.get(beforeHalfhour);
            }
            if(usingRoomMap.containsKey(appointTime)) {
                firstHalf += usingRoomMap.get(appointTime);
                secondHalf += usingRoomMap.get(appointTime);
            }
            if(usingRoomMap.containsKey(afterHalfhour)) {
                secondHalf += usingRoomMap.get(afterHalfhour);
            }
            
            int usingCount = firstHalf > secondHalf ? firstHalf : secondHalf;
            
            if(experience) {
                String afterHour = DateFormatUtils.format(org.apache.commons.lang3.time.DateUtils.addHours(current, 1), "HH:mm");
                int thirdHalf = 0;
                
                if(usingExperienceRoomMap.containsKey(appointTime)) {
                    thirdHalf += usingExperienceRoomMap.get(appointTime);
                }
                
                if(usingRoomMap.containsKey(afterHalfhour)) {
                    thirdHalf += usingRoomMap.get(afterHalfhour);
                }
                if(usingRoomMap.containsKey(afterHour)) {
                    thirdHalf += usingRoomMap.get(afterHour);
                }
                
                usingCount = usingCount > thirdHalf ? usingCount : thirdHalf;
            }
            
            remainRooms.put(appointTime, totalRoom > usingCount ? totalRoom - usingCount : 0);
        }
        
        return remainRooms;
    }

    private Map<String, Integer> remainRoom(RoomType roomType, Site site, Date day, List<String> appointTimes, boolean experience, CourseSchedule courseSchedule) {
        Map<String, Integer> remainRooms = new HashMap<>();
        
        Map<String, Integer> usingRoomMap = new HashMap<>();
        
        List<Map<String, Object>> usingRoomList = new ArrayList<>();
        if(roomType == courseSchedule.getRoomType() && DateUtils.isSameDay(day, courseSchedule.getOccurDate())) {
            usingRoomList = courseScheduleDao.list(roomType, site, day, courseSchedule);
        } else {
            usingRoomList = courseScheduleDao.list(roomType, site, day);
        }
        if(!usingRoomList.isEmpty()) {
            for(Map<String, Object> map : usingRoomList) {
                usingRoomMap.put((String)map.get("occurHour"), ((Long)map.get("usingRoom")).intValue());
            }
        }
        
        Map<String, Integer> usingExperienceRoomMap = new HashMap<>();
        
        List<Map<String, Object>> usingExperienceRoomList = new ArrayList<>();
        if(courseSchedule.getCourse().isExperience() && roomType == courseSchedule.getRoomType() && DateUtils.isSameDay(day, courseSchedule.getOccurDate())) {
            usingExperienceRoomList = courseScheduleDao.listExperience(roomType, site, day, courseSchedule);
        } else {
            usingExperienceRoomList = courseScheduleDao.listExperience(roomType, site, day);
        }
        if(!usingExperienceRoomList.isEmpty()) {
            for(Map<String, Object> map : usingExperienceRoomList) {
                usingExperienceRoomMap.put((String)map.get("occurHour"), ((Long)map.get("usingRoom")).intValue());
            }
        }
        
        int totalRoom = site.getLimit(roomType);
        for(String appointTime : appointTimes) {
            Date current = DateUtils.parseDate(DateFormatUtils.format(day, "yyyy-MM-dd ") + appointTime + ":00", "yyyy-MM-dd HH:mm:ss");
            String beforeHour = DateFormatUtils.format(org.apache.commons.lang3.time.DateUtils.addHours(current, -1), "HH:mm");
            String beforeHalfhour = DateFormatUtils.format(org.apache.commons.lang3.time.DateUtils.addMinutes(current, -30), "HH:mm");
            String afterHalfhour = DateFormatUtils.format(org.apache.commons.lang3.time.DateUtils.addMinutes(current, 30), "HH:mm");
            
            int firstHalf = 0,secondHalf = 0;
            
            if(usingExperienceRoomMap.containsKey(beforeHour)) {
                firstHalf += usingExperienceRoomMap.get(beforeHour);
            }
            if(usingExperienceRoomMap.containsKey(beforeHalfhour)) {
                secondHalf += usingExperienceRoomMap.get(beforeHalfhour);
            }
            
            if(usingRoomMap.containsKey(beforeHalfhour)) {
                firstHalf += usingRoomMap.get(beforeHalfhour);
            }
            if(usingRoomMap.containsKey(appointTime)) {
                firstHalf += usingRoomMap.get(appointTime);
                secondHalf += usingRoomMap.get(appointTime);
            }
            if(usingRoomMap.containsKey(afterHalfhour)) {
                secondHalf += usingRoomMap.get(afterHalfhour);
            }
            
            int usingCount = firstHalf > secondHalf ? firstHalf : secondHalf;
            
            if(experience) {
                String afterHour = DateFormatUtils.format(org.apache.commons.lang3.time.DateUtils.addHours(current, 1), "HH:mm");
                int thirdHalf = 0;
                
                if(usingExperienceRoomMap.containsKey(appointTime)) {
                    thirdHalf += usingExperienceRoomMap.get(appointTime);
                }
                
                if(usingRoomMap.containsKey(afterHalfhour)) {
                    thirdHalf += usingRoomMap.get(afterHalfhour);
                }
                if(usingRoomMap.containsKey(afterHour)) {
                    thirdHalf += usingRoomMap.get(afterHour);
                }
                
                usingCount = usingCount > thirdHalf ? usingCount : thirdHalf;
            }
            
            remainRooms.put(appointTime, totalRoom > usingCount ? totalRoom - usingCount : 0);
        }
        
        return remainRooms;
    }

    /**
     * 获取可预约时间
     * @param courseRound
     * @param site
     * @param day
     * @return
     */
    private List<String> getAppointTimes(CourseRound courseRound, Site site, Date day) {
        List<String> appointTimes = new ArrayList<>();
        
        Date now = new Date();
        
        String appointStartHour = site.getAppointStartHour();
        String appointEndHour = site.getAppointEndHour();
        Date appointStart = DateUtils.parseDate(DateFormatUtils.format(day, "yyyy-MM-dd ") + appointStartHour + ":00", "yyyy-MM-dd HH:mm:ss");
        Date appointEnd = DateUtils.parseDate(DateFormatUtils.format(day, "yyyy-MM-dd ") + appointEndHour + ":00", "yyyy-MM-dd HH:mm:ss");
        
        if(StringUtils.isNotEmpty(courseRound.getCourse().getAppointStartTime())) {
            if(DateUtils.parseDate(DateFormatUtils.format(day, "yyyy-MM-dd ") + courseRound.getCourse().getAppointStartTime() + ":00", "yyyy-MM-dd HH:mm:ss").after(appointStart)) {
                appointStartHour = courseRound.getCourse().getAppointStartTime();
                appointStart = DateUtils.parseDate(DateFormatUtils.format(day, "yyyy-MM-dd ") + appointStartHour + ":00", "yyyy-MM-dd HH:mm:ss");
            }
        }
        
        if(StringUtils.isNotEmpty(courseRound.getCourse().getAppointEndTime())) {
            if(DateUtils.parseDate(DateFormatUtils.format(day, "yyyy-MM-dd ") + courseRound.getCourse().getAppointEndTime() + ":00", "yyyy-MM-dd HH:mm:ss").before(appointEnd)) {
                appointEndHour = courseRound.getCourse().getAppointEndTime();
                appointEnd = DateUtils.parseDate(DateFormatUtils.format(day, "yyyy-MM-dd ") + appointEndHour + ":00", "yyyy-MM-dd HH:mm:ss");
            }
        }
        
        if(DateUtils.isSameDay(day, now) && now.after(appointStart)) {
            int hour = Integer.parseInt(DateFormatUtils.format(now, "HH"));
            int minute = Integer.parseInt(DateFormatUtils.format(now, "mm"));
            
            if(minute >= 30) {
                appointStartHour = ((hour + 1) < 10 ? "0" : "") + (hour + 1) + ":00";
            } else {
                appointStartHour = (hour < 10 ? "0" : "") + hour + ":30";
            }
            
            appointStart = DateUtils.parseDate(DateFormatUtils.format(day, "yyyy-MM-dd ") + appointStartHour + ":00", "yyyy-MM-dd HH:mm:ss");
        }
        
        if(courseRound.getEffectiveEnd() != null && DateUtils.isSameDay(day, courseRound.getEffectiveEnd()) && courseRound.getEffectiveEnd().before(appointEnd)) {
            appointEnd = courseRound.getEffectiveEnd();
        }
        
        for(Date appointTime = appointStart; !appointTime.after(org.apache.commons.lang3.time.DateUtils.addMinutes(appointEnd, courseRound.getCourse().isExperience() ? -90 : -60)); appointTime = org.apache.commons.lang3.time.DateUtils.addMinutes(appointTime, 30)) {
            appointTimes.add(DateFormatUtils.format(appointTime, "HH:mm"));
        }
        
        return appointTimes;
    }

    /**
     * 根据周几获取门店可选教练
     * @param site
     * @param day 1 周一,2 周二,3 周三,4 周四,5 周五, 6 周六,7 周日
     * @return
     */
    private List<Principal> listCoach(Site site, Date day) {
        return coachDao.list(site, "%" + CourseAppointmentUtils.getDayOfWeek(day) + "%");
    }

    /*
     * (non-Javadoc)
     * @see co.fitstart.weapp.api.service.CourseAppointService#listCoach(co.fitstart.entity.course.round.CourseRound, co.fitstart.entity.site.Site, java.util.Date, java.lang.String)
     */
    public Map<String, Object> listCoach(CourseRound courseRound, Site site, Date occurDate, String occurHour) {
        Map<String, Object> result  = new HashMap<>();
        
        result.put("commonAvailable", this.countAvailableRoom(RoomType.COMMON, site, occurDate, occurHour, courseRound.getCourse().isExperience()));
        result.put("roomAvailable", this.countAvailableRoom(RoomType.ROOM, site, occurDate, occurHour, courseRound.getCourse().isExperience()));
        
        List<Principal> coaches = this.listCoach(site, occurDate);
        
        if(!coaches.isEmpty()) {
            List<Map<String, Object>> coachList = new ArrayList<>();
            
            String afterHalfHour = this.getOccurHour(occurHour, 1);
            List<Principal> experienceScheduleCoaches = courseScheduleDao.listCoachBusyWithExperience(occurDate, this.getOccurHour(occurHour, -2), coaches);
            List<Principal> scheduleCoaches = new ArrayList<>();
            List<Principal> busyCoaches = new ArrayList<>();
            if(courseRound.getCourse().isExperience()) {
                String afterHour = this.getOccurHour(occurHour, 2);
                
                scheduleCoaches = courseScheduleDao.listBusyCoach(occurDate, Arrays.asList(new String[]{this.getOccurHour(occurHour, -1), occurHour, afterHalfHour, afterHour}), coaches);
                busyCoaches = coachDao.listBusyCoach(coaches, CoachRestType.DUTY, occurDate, Arrays.asList(new String[]{occurHour, afterHalfHour, afterHour}));
            } else {
                scheduleCoaches = courseScheduleDao.listBusyCoach(occurDate, Arrays.asList(new String[]{this.getOccurHour(occurHour, -1), occurHour, afterHalfHour}), coaches);
                busyCoaches = coachDao.listBusyCoach(coaches, CoachRestType.DUTY, occurDate, Arrays.asList(new String[]{occurHour, afterHalfHour}));
            }
            
            for(Principal coach : coaches) {
                Map<String, Object> map = new HashMap<>();
                map.put("id", coach.getId());
                map.put("avatar", coach.getProfile() == null ? null : coach.getProfile().getAvatar());
                map.put("name", coach.getName());
                map.put("specialty", coach.getProfile() == null ? null : coach.getProfile().getSpecialtyArray());
                map.put("available", !(experienceScheduleCoaches.contains(coach) || scheduleCoaches.contains(coach) || busyCoaches.contains(coach)));
                
                coachList.add(map);
            }
            
            result.put("coaches", coachList);
        }
        
        return result;
    }

    /*
     * (non-Javadoc)
     * @see co.fitstart.weapp.api.service.CourseAppointService#listCoach(co.fitstart.entity.course.round.CourseRound, co.fitstart.entity.site.Site, java.util.Date, java.lang.String, co.fitstart.entity.course.schedule.CourseSchedule)
     */
    public Map<String, Object> listCoach(CourseRound courseRound, Site site, Date occurDate, String occurHour,
            CourseSchedule courseSchedule) {
        Map<String, Object> result  = new HashMap<>();
        
        result.put("commonAvailable", this.countAvailableRoom(RoomType.COMMON, site, occurDate, occurHour, courseRound.getCourse().isExperience(), courseSchedule));
        result.put("roomAvailable", this.countAvailableRoom(RoomType.ROOM, site, occurDate, occurHour, courseRound.getCourse().isExperience(), courseSchedule));
        
        List<Principal> coaches = this.listCoach(site, occurDate);
        
        if(!coaches.isEmpty()) {
            List<Map<String, Object>> coachList = new ArrayList<>();
            
            String afterHalfHour = this.getOccurHour(occurHour, 1);
            
            List<String> scheduleOccurHours = new ArrayList<>();
            scheduleOccurHours.add(this.getOccurHour(occurHour, -1));
            scheduleOccurHours.add(occurHour);
            scheduleOccurHours.add(afterHalfHour);
            
            List<String> calendarRestHours = new ArrayList<>();
            calendarRestHours.add(occurHour);
            calendarRestHours.add(afterHalfHour);
            
            if(courseRound.getCourse().isExperience()) {
                String afterHour = this.getOccurHour(occurHour, 2);
                
                scheduleOccurHours.add(afterHour);
                calendarRestHours.add(afterHour);
            }
            
            List<Principal> experienceScheduleCoaches = courseScheduleDao.listCoachBusyWithExperience(occurDate, this.getOccurHour(occurHour, -2), coaches, courseSchedule);
            List<Principal> scheduleCoaches = courseScheduleDao.listBusyCoach(occurDate, scheduleOccurHours, coaches, courseSchedule);
            List<Principal> busyCoaches = new ArrayList<>();
            if(DateUtils.isSameDay(occurDate, courseSchedule.getOccurDate()) && courseSchedule.getCourse().isExperience()) {
                CoachRestCalendar experienceCalerdar = coachDao.loadCoachRestCalendar(courseSchedule.getCoach(), CoachRestType.EXPERIENCE, occurDate, this.getOccurHour(courseSchedule.getOccurHour(), 2));
                if(experienceCalerdar == null) {
                    busyCoaches = coachDao.listBusyCoach(coaches, CoachRestType.DUTY, occurDate, calendarRestHours);
                } else {
                    busyCoaches = coachDao.listBusyCoach(coaches, CoachRestType.DUTY, occurDate, calendarRestHours, experienceCalerdar);
                }
            } else {
                busyCoaches = coachDao.listBusyCoach(coaches, CoachRestType.DUTY, occurDate, calendarRestHours);
            }
            
            for(Principal coach : coaches) {
                Map<String, Object> map = new HashMap<>();
                map.put("id", coach.getId());
                map.put("avatar", coach.getProfile() == null ? null : coach.getProfile().getAvatar());
                map.put("name", coach.getName());
                map.put("specialty", coach.getProfile() == null ? null : coach.getProfile().getSpecialtyArray());
                
                if(courseSchedule.getSite() == site && courseSchedule.getCoach() == coach && DateUtils.isSameDay(occurDate, courseSchedule.getOccurDate()) && occurHour.equals(courseSchedule.getOccurHour())) {
                    map.put("available", false);
                    map.put("current", true);
                } else {
                    map.put("available", !(experienceScheduleCoaches.contains(coach) || scheduleCoaches.contains(coach) || busyCoaches.contains(coach)));
                    map.put("current", false);
                }
                
                coachList.add(map);
            }
            
            result.put("coaches", coachList);
        }
        
        return result;
    }

    /**
     * 
     * @param roomType
     * @param site
     * @param occurDate
     * @param occurHour
     * @param experience
     * @return
     */
    private int countAvailableRoom(RoomType roomType, Site site, Date occurDate, String occurHour, boolean experience) {
        if(site.getLimit(roomType) > 0) {
            String beforeHour = this.getOccurHour(occurHour, -2);
            String beforeHalfHour = this.getOccurHour(occurHour, -1);
            String afterHalfHour = this.getOccurHour(occurHour, 1);
            
            int beforeHourExperienceCount = courseScheduleDao.countUsingRoomForExperience(roomType, site, occurDate, beforeHour);
            int beforeHalfHourExperienceCount = courseScheduleDao.countUsingRoomForExperience(roomType, site, occurDate, beforeHalfHour);
            int beforeHalfHourCount = courseScheduleDao.countUsingRoom(roomType, site, occurDate, beforeHalfHour);
            int occurHourCount = courseScheduleDao.countUsingRoom(roomType, site, occurDate, occurHour);
            int afterHalfHourCount = courseScheduleDao.countUsingRoom(roomType, site, occurDate, afterHalfHour);
            
            int firstHalfHourCount = beforeHourExperienceCount + beforeHalfHourCount + occurHourCount;
            int secondHalfHourCount = beforeHalfHourExperienceCount + occurHourCount + afterHalfHourCount;
            
            int usingCount = firstHalfHourCount > secondHalfHourCount ? firstHalfHourCount : secondHalfHourCount;
            
            if(experience) {
                String afterHour = this.getOccurHour(occurHour, 2);
                
                int occurHourExperienceCount = courseScheduleDao.countUsingRoomForExperience(roomType, site, occurDate, occurHour);
                int afterHourCount = courseScheduleDao.countUsingRoom(roomType, site, occurDate, afterHour);
                
                int thirdHalfHourCount = occurHourExperienceCount + afterHalfHourCount + afterHourCount;
                
                if(usingCount < thirdHalfHourCount) {
                    usingCount = thirdHalfHourCount;
                }
            }
            
            return site.getLimit(roomType) > usingCount ? site.getLimit(roomType) - usingCount : 0;
        } else {
            return 0;
        }
    }

    /**
     * 
     * @param roomType
     * @param site
     * @param occurDate
     * @param occurHour
     * @param experience
     * @param courseSchedule
     * @return
     */
    private int countAvailableRoom(RoomType roomType, Site site, Date occurDate, String occurHour, boolean experience, CourseSchedule courseSchedule) {
        if(site.getLimit(roomType) > 0) {
            String beforeHour = this.getOccurHour(occurHour, -2);
            String beforeHalfHour = this.getOccurHour(occurHour, -1);
            String afterHalfHour = this.getOccurHour(occurHour, 1);
            
            int beforeHourExperienceCount = courseScheduleDao.countUsingRoomForExperience(roomType, site, occurDate, beforeHour);
            int beforeHalfHourExperienceCount = courseScheduleDao.countUsingRoomForExperience(roomType, site, occurDate, beforeHalfHour);
            int beforeHalfHourCount = courseScheduleDao.countUsingRoom(roomType, site, occurDate, beforeHalfHour);
            int occurHourCount = courseScheduleDao.countUsingRoom(roomType, site, occurDate, occurHour);
            int afterHalfHourCount = courseScheduleDao.countUsingRoom(roomType, site, occurDate, afterHalfHour);
            
            if(courseSchedule.getSite() == site && DateUtils.isSameDay(courseSchedule.getOccurDate(), occurDate) && courseSchedule.getRoomType() == roomType) {
                if(courseSchedule.getOccurHour().equals(beforeHour) && courseSchedule.getCourse().isExperience()) {
                    beforeHourExperienceCount--;
                } else if(courseSchedule.getOccurHour().equals(beforeHalfHour)) {
                    beforeHalfHourCount--;
                    
                    if(courseSchedule.getCourse().isExperience()) {
                        beforeHalfHourExperienceCount --;
                    }
                } else if(courseSchedule.getOccurHour().equals(occurHour)) {
                    occurHourCount--;
                } else if(courseSchedule.getOccurHour().equals(afterHalfHour)) {
                    afterHalfHourCount--;
                }
            }
            
            int firstHalfHourCount = beforeHourExperienceCount + beforeHalfHourCount + occurHourCount;
            int secondHalfHourCount = beforeHalfHourExperienceCount + occurHourCount + afterHalfHourCount;
            
            int usingCount = firstHalfHourCount > secondHalfHourCount ? firstHalfHourCount : secondHalfHourCount;
            
            if(experience) {
                String afterHour = this.getOccurHour(occurHour, 2);
                
                int occurHourExperienceCount = courseScheduleDao.countUsingRoomForExperience(roomType, site, occurDate, occurHour);
                int afterHourCount = courseScheduleDao.countUsingRoom(roomType, site, occurDate, afterHour);
                
                if(courseSchedule.getSite() == site && DateUtils.isSameDay(courseSchedule.getOccurDate(), occurDate) && courseSchedule.getRoomType() == roomType) {
                    if(courseSchedule.getOccurHour().equals(occurHour) && courseSchedule.getCourse().isExperience()) {
                        occurHourExperienceCount--;
                    } else if(courseSchedule.getOccurHour().equals(afterHour)) {
                        afterHourCount--;
                    }
                }
                
                int thirdHalfHourCount = occurHourExperienceCount + afterHalfHourCount + afterHourCount;
                
                if(usingCount < thirdHalfHourCount) {
                    usingCount = thirdHalfHourCount;
                }
            }
            
            return site.getLimit(roomType) > usingCount ? site.getLimit(roomType) - usingCount : 0;
        } else {
            return 0;
        }
    }

    /**
     * 根据约课时间获取前后半小时
     * @param occurHour
     * @param add 半小时为一个单位
     * @return
     */
    public String getOccurHour(String occurHour, int add) {
        String[] arr = occurHour.split(":");
        int hour = Integer.parseInt(arr[0]);
        
        if(add%2 == 0) {
            return (hour + add/2) + ":" + arr[1];
        } else {
            if("00".equals(arr[1])) {
                if(add < 0) {
                    return (hour + add/2 - 1) + ":30";
                } else {
                    return (hour + add/2) + ":30";
                }
            } else {
                if(add < 0) {
                    return (hour + add/2) + ":00";
                } else {
                    return (hour + add/2 + 1) + ":00";
                }
                
            }
        }
    }

    /*
     * (non-Javadoc)
     * @see co.fitstart.weapp.api.service.CourseAppointService#listRemainByCoach(co.fitstart.entity.course.round.CourseRound, co.fitstart.entity.site.Site, java.util.Date, co.fitstart.entity.principal.Principal)
     */
    public List<Map<String, Object>> listRemainByCoach(CourseRound courseRound, Site site, Date occurDate,
            Principal coach) {
        List<Map<String, Object>> remains = new ArrayList<>();
        
        if(this.isAvailable(occurDate, site, coach)) {
            List<String> appointTimes = this.getAppointTimes(courseRound, site, occurDate);
            if(appointTimes.size() > 0) {
                boolean experience = courseRound.getCourse().isExperience();
                
                List<String> coachAvailableTimes = this.listAvailableAppointTime(coach, occurDate, appointTimes, experience);
                
                if(!coachAvailableTimes.isEmpty()) {
                  //剩余私人区域
                    Map<String, Integer> remainRooms = this.remainRoom(RoomType.ROOM, site, occurDate, coachAvailableTimes, experience);
                    //剩余公共区域
                    Map<String,Integer> remainCommons = this.remainRoom(RoomType.COMMON, site, occurDate, coachAvailableTimes, experience);
                    
                    for(String appointTime : coachAvailableTimes) {
                        int remainRoom = remainRooms.get(appointTime);
                        int remainCommon = remainCommons.get(appointTime);
                        
                        if(remainRoom > 0 || remainCommon > 0) {
                            Map<String, Object> map = new HashMap<>();
                            map.put("occurHour", appointTime);
                            map.put("endHour", this.getOccurHour(appointTime, courseRound.getCourse().isExperience() ? 3 : 2));
                            
                            map.put("roomAvailable", remainRoom);
                            map.put("commonAvailable", remainCommon);
                            
                            remains.add(map);
                        }
                    }
                }
            }
        }
        
        return remains;
    }

    /*
     * (non-Javadoc)
     * @see co.fitstart.weapp.api.service.CourseAppointService#listRemainByCoach(co.fitstart.entity.course.round.CourseRound, co.fitstart.entity.site.Site, java.util.Date, co.fitstart.entity.principal.Principal, co.fitstart.entity.course.schedule.CourseSchedule)
     */
    public List<Map<String, Object>> listRemainByCoach(CourseRound courseRound, Site site, Date occurDate,
            Principal coach, CourseSchedule courseSchedule) {
        List<Map<String, Object>> remains = new ArrayList<>();
        
        if(this.isAvailable(occurDate, site, coach)) {
            List<String> appointTimes = this.getAppointTimes(courseRound, site, occurDate);
            if(appointTimes.size() > 0) {
                boolean experience = courseRound.getCourse().isExperience();
                
                List<String> coachAvailableTimes = this.listAvailableAppointTime(coach, occurDate, appointTimes, experience, courseSchedule);
                
                if(!coachAvailableTimes.isEmpty()) {
                    //剩余私人区域
                    Map<String, Integer> remainRooms = this.remainRoom(RoomType.ROOM, site, occurDate, coachAvailableTimes, experience, courseSchedule);
                    //剩余公共区域
                    Map<String,Integer> remainCommons = this.remainRoom(RoomType.COMMON, site, occurDate, coachAvailableTimes, experience, courseSchedule);
                    
                    for(String appointTime : coachAvailableTimes) {
                        if(courseSchedule.getSite() != site || courseSchedule.getCoach() != coach || !DateUtils.isSameDay(occurDate, courseSchedule.getOccurDate()) || !courseSchedule.getOccurHour().equals(appointTime)) {
                            int remainRoom = remainRooms.get(appointTime);
                            int remainCommon = remainCommons.get(appointTime);
                            
                            if(remainRoom > 0 || remainCommon > 0) {
                                Map<String, Object> map = new HashMap<>();
                                map.put("occurHour", appointTime);
                                map.put("endHour", this.getOccurHour(appointTime, courseRound.getCourse().isExperience() ? 3 : 2));
                                
                                map.put("roomAvailable", remainRoom);
                                map.put("commonAvailable", remainCommon);
                                
                                remains.add(map);
                            }
                        }
                    }
                }
            }
        }
        
        return remains;
    }

    /**
     * 当前日期教练在指定门店是否可预约
     * @param day
     * @param site
     * @param coach
     * @return true 可预约; false 不可预约
     */
    private boolean isAvailable(Date day, Site site, Principal coach) {
        int dayOfWeek = CourseAppointmentUtils.getDayOfWeek(day);
        
        return coachDao.load(site, "%" + dayOfWeek + "%", coach) != null;
    }

    /**
     * 
     * @param coach
     * @param occurDate
     * @param appointTimes
     * @param experience
     * @return
     */
    @SuppressWarnings("unchecked")
    private List<String> listAvailableAppointTime(Principal coach, Date occurDate, List<String> appointTimes, boolean experience) {
        List<Map<String, Object>> schedules = courseScheduleDao.listScheduleHour(coach, occurDate);
        
        Map<String, Object> parameters = new HashMap<>();
        parameters.put("duty", CoachRestType.DUTY);
        parameters.put("coach", coach);
        parameters.put("restDate", occurDate);
        List<String> calendarHours = genericDaoSupport.searchForList("SELECT restHour FROM CoachRestCalendar WHERE disabled = false AND coachRestType != :duty AND coach = :coach AND restDate = :restDate", parameters);
        
        if(!schedules.isEmpty() || !calendarHours.isEmpty()) {
            List<String> availableAppointTimes = new ArrayList<>();
            
            Map<String, Boolean> scheduleHours  = new HashMap<>();
            
            for(Map<String, Object> map : schedules) {
                scheduleHours.put((String)map.get("occurHour"), (boolean)map.get("experience"));
            }
            
            for(String appointTime : appointTimes) {
                String beforeHour = this.getOccurHour(appointTime, -2);
                String beforeHalfHour = this.getOccurHour(appointTime, -1);
                String afterHalfHour = this.getOccurHour(appointTime, 1);
                
                if((!scheduleHours.containsKey(beforeHour) || !scheduleHours.get(beforeHour)) && !scheduleHours.containsKey(beforeHalfHour) && !scheduleHours.containsKey(appointTime) && !scheduleHours.containsKey(afterHalfHour) && !calendarHours.contains(appointTime) && !calendarHours.contains(afterHalfHour)) {
                    if(experience) {
                        String afterHour = this.getOccurHour(appointTime, 2);
                        if(!scheduleHours.containsKey(afterHour) && !calendarHours.contains(afterHour)) {
                            availableAppointTimes.add(appointTime);
                        }
                    } else {
                        availableAppointTimes.add(appointTime);
                    }
                }
            }
            
            return availableAppointTimes;
        } else {
            return appointTimes;
        }
    }

    /**
     * 
     * @param coach
     * @param occurDate
     * @param appointTimes
     * @param experience
     * @param courseSchedule
     * @return
     */
    @SuppressWarnings("unchecked")
    private List<String> listAvailableAppointTime(Principal coach, Date occurDate, List<String> appointTimes, boolean experience, CourseSchedule courseSchedule) {
        List<Map<String, Object>> schedules = courseScheduleDao.listScheduleHour(coach, occurDate, courseSchedule);
        List<String> calendarHours = new ArrayList<>();
        
        Map<String, Object> parameters = new HashMap<>();
        parameters.put("duty", CoachRestType.DUTY);
        parameters.put("coach", coach);
        parameters.put("restDate", occurDate);
        if(courseSchedule.getCourse().isExperience()) {
            CoachRestCalendar calendar = coachDao.loadCoachRestCalendar(coach, CoachRestType.EXPERIENCE, occurDate, this.getOccurHour(courseSchedule.getOccurHour(), 2));
            
            if(calendar != null) {
                parameters.put("calendar", calendar);
                
                calendarHours = genericDaoSupport.searchForList("SELECT calendar.restHour FROM CoachRestCalendar calendar WHERE calendar.disabled = false AND calendar.coachRestType != :duty AND calendar.coach = :coach AND calendar.restDate = :restDate AND calendar != :calendar", parameters);
            } else {
                calendarHours = genericDaoSupport.searchForList("SELECT restHour FROM CoachRestCalendar WHERE disabled = false AND coachRestType != :duty AND coach = :coach AND restDate = :restDate", parameters);
            }
        } else {
            calendarHours = genericDaoSupport.searchForList("SELECT restHour FROM CoachRestCalendar WHERE disabled = false AND coachRestType != :duty AND coach = :coach AND restDate = :restDate", parameters);
        }
        
        if(!schedules.isEmpty() || !calendarHours.isEmpty()) {
            List<String> availableAppointTimes = new ArrayList<>();
            
            Map<String, Boolean> scheduleHours  = new HashMap<>();
            
            for(Map<String, Object> map : schedules) {
                scheduleHours.put((String)map.get("occurHour"), (boolean)map.get("experience"));
            }
            
            for(String appointTime : appointTimes) {
                String beforeHour = this.getOccurHour(appointTime, -2);
                String beforeHalfHour = this.getOccurHour(appointTime, -1);
                String afterHalfHour = this.getOccurHour(appointTime, 1);
                
                if((!scheduleHours.containsKey(beforeHour) || !scheduleHours.get(beforeHour)) && !scheduleHours.containsKey(beforeHalfHour) && !scheduleHours.containsKey(appointTime) && !scheduleHours.containsKey(afterHalfHour) && !calendarHours.contains(appointTime) && !calendarHours.contains(afterHalfHour)) {
                    if(experience) {
                        String afterHour = this.getOccurHour(appointTime, 2);
                        if(!scheduleHours.containsKey(afterHour) && !calendarHours.contains(afterHour)) {
                            availableAppointTimes.add(appointTime);
                        }
                    } else {
                        availableAppointTimes.add(appointTime);
                    }
                }
            }
            
            return availableAppointTimes;
        } else {
            return appointTimes;
        }
    }

    /*
     * (non-Javadoc)
     * @see co.fitstart.weapp.api.service.CourseAppointService#appointSchedule(co.fitstart.entity.course.round.CourseRound, co.fitstart.entity.site.Site, co.fitstart.entity.principal.Principal, java.util.Date, java.lang.String)
     */
    public void appointSchedule(CourseRound courseRound, Site site, Principal coach, Date occurDate, String occurHour) {
        if(site.getLimit(RoomType.ROOM) > 0) {
            if(this.countAvailableRoom(RoomType.ROOM, site, occurDate, occurHour, courseRound.getCourse().isExperience()) > 0) {
                this.appointSchedule(courseRound, site, coach, occurDate, occurHour, RoomType.ROOM);
            } else {
                this.appointSchedule(courseRound, site, coach, occurDate, occurHour, RoomType.COMMON);
            }
        } else {
            this.appointSchedule(courseRound, site, coach, occurDate, occurHour, RoomType.COMMON);
        }
    }

    /*
     * (non-Javadoc)
     * @see co.fitstart.weapp.api.service.CourseAppointService#appointSchedule(co.fitstart.entity.course.round.CourseRound, co.fitstart.entity.site.Site, co.fitstart.entity.principal.Principal, java.util.Date, java.lang.String, co.fitstart.entity.site.RoomType)
     */
    public void appointSchedule(CourseRound courseRound, Site site, Principal coach, Date occurDate, String occurHour,
            RoomType roomType) {
        this.validateAppointment(courseRound, site, coach, occurDate, occurHour, roomType);
        
        RoomScheduleRecord roomScheduleRecord = new RoomScheduleRecord(site, courseRound.getCode(), occurDate, occurHour, roomType, 1);
        genericDaoSupport.save(roomScheduleRecord);
        
        CourseSchedule courseSchedule = null;
        if(courseRound.getCourseType().isYear()) {
            courseSchedule = new CourseSchedule(courseRound, site, coach, occurDate, occurHour);
            genericDaoSupport.save(courseSchedule.roomScheduleRecord(roomScheduleRecord));
        } else if(courseRound.getCourseType().isIndividual()) {
            courseSchedule = courseScheduleDao.load(courseRound, ScheduleStatus.SCHEDULE);
            if(courseSchedule != null) {
                genericDaoSupport.update(courseSchedule.editTime(occurDate, occurHour).changeStatus(ScheduleStatus.READY).roomScheduleRecord(roomScheduleRecord).editCoach(coach).editSite(site));
                
                if(courseRound.getCourse().isExperience()) {
                    CoachRestCalendar calendar = new CoachRestCalendar(coach, CoachRestType.EXPERIENCE, occurDate, this.getOccurHour(occurHour, 2), false);
                    genericDaoSupport.save(calendar);
                }
            } else {
                throw new BusinessException("无可预约课时");
            }
        }
        
        if(courseRound.getCourseStatus().isPrepare()) {
            genericDaoSupport.update(courseRound.readyCourse().initPeriod(DateUtils.parseDate(DateFormatUtils.format(new Date(), "yyyy-MM-dd"), "yyyy-MM-dd")));
            genericDaoSupport.update(courseRound.getOrder().orderStatus(OrderStatus.PROCESSING));
            
            if(courseRound.getFitnessGroup() != null && !MapUtils.isEmpty(courseRound.getAttrs()) && courseRound.getAttrs().get(Constant.ONLINE_PLAN_AMOUNT) != null) {
                List<BatchCourseware> batches = coursewareDao.listBatchCourseware(courseRound, BatchCoursewareType.WEEKLY);
                
                Date effectiveStart = DateUtils.parseDate(DateFormatUtils.format(courseRound.getEffectiveStart(), "yyyy-MM-dd"), "yyyy-MM-dd");
                Date effectiveEnd = DateUtils.parseDate(DateFormatUtils.format(courseRound.getEffectiveEnd(), "yyyy-MM-dd"), "yyyy-MM-dd");
                
                for(BatchCourseware batch : batches) {
                    Date begin = DateUtils.addWeeks(effectiveStart, batch.getSorting() - 1);
                    Date end = DateUtils.addDays(begin, 6);
                    
                    if(end.after(effectiveEnd)) {
                        end = effectiveEnd;
                    }
                    
                    genericDaoSupport.update(batch.edit(begin, end));
                }
            }
            
            if(courseRound.getCourseType().isIndividual() || courseRound.getCourseType().isYear()) {
                if(courseRound.getCourse().isExperience()) {
                    smsSender.sendMessage(courseRound.getStudent().getMobile(), "【人马君】" + courseRound.getStudent().getName() + "小姐/先生您好,您预约了" + courseSchedule.getDayhour() + "的人马君体验课程。您本次的体验课教练是" + coach.getName() +"教练,如果您对教练有特殊的要求,可以直接电话沟通,教练手机为" + coach.getMobile() + "。<br>您需要穿着舒适的运动服装,人马君提供洗澡的地方(有拖鞋),如需洗澡可以自带毛巾及换洗衣物。可以自带一个杯子(支持环保),如果忘记我们也有一次性水杯提供。<br>人马君期待您的到来,希望带给你一个不一样的健身体验,谢谢!");
                }
                smsSender.sendMessage(courseRound.getStudent().getMobile(), "【人马君】尊敬的人马君学员,您好!您已成功预约第一次的上课,时间是" + courseSchedule.getDayhour() + ",选择的区域是" + courseSchedule.getRoomType().getDescription() + "#,请提前15分钟至店,换好运动鞋服,携带好水杯。训练前可根据主教练要求摄入适量食物。好好享受这一个小时多巴胺带来的快乐,您将在人马君遇见更好的自己。");
            }
        }

        CoachStudentRelation coachStudentRelation = courseScheduleDao.loadCoachStudentRelation(coach, courseRound.getStudent(), courseRound.getCourseType());
        if(coachStudentRelation == null) {
            coachStudentRelation = new CoachStudentRelation(coach, courseSchedule);
        }
        genericDaoSupport.saveOrUpdate(coachStudentRelation.editSchedule(courseSchedule));
        
        this.createScheduleSmsNotice(courseSchedule);
        
        // create new principal message
        String message = "您有新的单节预约课程，课程时间为【"+ DateFormatUtils.format(occurDate, "yyyy-MM-dd") + " " + occurHour +"】，学员为【" + courseRound.getStudent().getName() + "】，主教练为【" + courseRound.getCoach().getName() + "】，请准时去上课。";
        
        PrincipalMessage principalMessage = new PrincipalMessage(coach);
        genericDaoSupport.save(principalMessage.initialize("新课程预约", "您有新的单节预约课程", message));
        
        tencentHandler.sendCorpMessage(message, coach.getMobile());
        
        this.recalculateStudentCourseware(courseSchedule.getCourseRound());
    }

    /*
     * (non-Javadoc)
     * @see co.fitstart.weapp.api.service.CourseAppointService#changeSchedule(co.fitstart.entity.course.round.CourseRound, co.fitstart.entity.site.Site, co.fitstart.entity.principal.Principal, java.util.Date, java.lang.String, co.fitstart.entity.course.schedule.CourseSchedule)
     */
    public void changeSchedule(CourseRound courseRound, Site site, Principal coach, Date occurDate, String occurHour, CourseSchedule courseSchedule) {
        if(site.getLimit(RoomType.ROOM) > 0) {
            if(this.countAvailableRoom(RoomType.ROOM, site, occurDate, occurHour, courseRound.getCourse().isExperience(), courseSchedule) > 0) {
                this.changeSchedule(courseRound, site, coach, occurDate, occurHour, RoomType.ROOM, courseSchedule);
            } else {
                this.changeSchedule(courseRound, site, coach, occurDate, occurHour, RoomType.COMMON, courseSchedule);
            }
        } else {
            this.changeSchedule(courseRound, site, coach, occurDate, occurHour, RoomType.COMMON, courseSchedule);
        }
    }

    /*
     * (non-Javadoc)
     * @see co.fitstart.weapp.api.service.CourseAppointService#changeSchedule(co.fitstart.entity.course.round.CourseRound, co.fitstart.entity.site.Site, co.fitstart.entity.principal.Principal, java.util.Date, java.lang.String, co.fitstart.entity.site.RoomType, co.fitstart.entity.course.schedule.CourseSchedule)
     */
    public void changeSchedule(CourseRound courseRound, Site site, Principal coach, Date occurDate, String occurHour,
            RoomType roomType, CourseSchedule courseSchedule) {
        this.validateAppointment(courseRound, site, coach, occurDate, occurHour, roomType, courseSchedule);
        
        RoomScheduleRecord roomScheduleRecord = courseSchedule.getRoomSchedule();
        if(roomScheduleRecord != null) {
            genericDaoSupport.update(roomScheduleRecord.updateTime(occurDate, occurHour, site, roomType));
        }else {
            roomScheduleRecord = new RoomScheduleRecord(site, courseSchedule.getCourseRound().getCode(), occurDate, occurHour, roomType, 1);
            genericDaoSupport.save(roomScheduleRecord);
        }
        
        Principal cancelCoach = courseSchedule.getCoach();
        if(cancelCoach != coach) {
            String message = "您的学员" + courseSchedule.getStudent().getName() + "，有课程取消："
                    + DateFormatUtils.format(courseSchedule.getOccurDate()) + " " + courseSchedule.getOccurHour() 
                    + "已被学员取消"
                    + "，请去系统查看。";
            PrincipalMessage cancelMessage = new PrincipalMessage(cancelCoach);
            genericDaoSupport.save(cancelMessage.initialize("课程变更", "您有新的课程变更信息", message));
            
            tencentHandler.sendCorpMessage(message, cancelCoach.getMobile());
        }
        
        if(courseSchedule.getCourseType().isYear() || (courseSchedule.getCourseType().isIndividual() && !courseSchedule.getCourse().isExperience())) {
            if(DateUtils.isSameDay(new Date(), courseSchedule.getOccurDate()) && !DateUtils.isSameDay(new Date(), occurDate)) {
                CourseScheduleCancelRecord record = courseScheduleDao.loadCourseScheduleCancelRecord(courseSchedule, courseSchedule.getOccurDate());
                
                if(record == null) {
                    record = new CourseScheduleCancelRecord(courseSchedule, false);
                    
                    genericDaoSupport.save(record);
                }
            }
        }
        
        if(courseSchedule.getCourse().isExperience()) {
            CoachRestCalendar calendar = coachDao.loadCoachRestCalendar(cancelCoach, CoachRestType.EXPERIENCE, courseSchedule.getOccurDate(), this.getOccurHour(courseSchedule.getOccurHour(), 2));
            
            if(calendar == null) {
                calendar = new CoachRestCalendar(coach, CoachRestType.EXPERIENCE, occurDate, this.getOccurHour(occurHour, 2), false);
                genericDaoSupport.save(calendar);
            } else {
                genericDaoSupport.update(calendar.edit(coach, occurDate, this.getOccurHour(occurHour, 2)).editAdminExperience(false));
            }
        }
        
        courseSchedule.editCoach(coach)
                      .editTime(occurDate, occurHour)
                      .editSite(site)
                      .roomScheduleRecord(roomScheduleRecord);
        genericDaoSupport.update(courseSchedule);
        
        if(cancelCoach != coach) {
            String message = "您有新的单节课程，学员为【" + courseSchedule.getStudent().getName() + "】，上课时间为【"
                    + courseSchedule.getDayhour() 
                    +"】，主教练为【" + courseSchedule.getCourseRound().getCoach().getName() + "】，请在我的学员点击查看，并及时上课。";
            PrincipalMessage newMessage = new PrincipalMessage(courseSchedule.getCoach());
            genericDaoSupport.save(newMessage.initialize("新单节课程", "您有新的单节课程", message));
            
            tencentHandler.sendCorpMessage(message, coach.getMobile());
        } else {
            String message = "您有新的单节课程时间已被修改，学员为【" + courseSchedule.getStudent().getName() + "】，上课时间为【"
                    + courseSchedule.getDayhour() 
                    +"】，主教练为【" + courseSchedule.getCourseRound().getCoach().getName() + "】，请在我的学员点击查看，并及时上课。";
            PrincipalMessage updateMessage = new PrincipalMessage(coach);
            genericDaoSupport.save(updateMessage.initialize("单节课程改课时间", "您有新的单节课程时间", message));
            
            tencentHandler.sendCorpMessage(message, coach.getMobile());
        }
        
        this.createScheduleSmsNotice(courseSchedule);
        
        this.recalculateStudentCourseware(courseSchedule.getCourseRound());
    }

    /**
     * 
     * @param courseSchedule
     */
    private void createScheduleSmsNotice(CourseSchedule courseSchedule) {
        if((courseSchedule.getCourseType().isIndividual() || courseSchedule.getCourseType().isYear()) && !courseSchedule.getCourse().isExperience()) {
            courseScheduleDao.disableScheduleSmsNotice(courseSchedule);
            
            ScheduleSmsNotice notice = new ScheduleSmsNotice(courseSchedule);
            
            genericDaoSupport.save(notice);
        }
    }

    /*
     * (non-Javadoc)
     * @see co.fitstart.weapp.api.service.CourseAppointService#validateAppointment(co.fitstart.entity.course.round.CourseRound, co.fitstart.entity.site.Site, co.fitstart.entity.principal.Principal, java.util.Date, java.lang.String, co.fitstart.entity.site.RoomType)
     */
    public void validateAppointment(CourseRound courseRound, Site site, Principal coach, Date occurDate,
            String occurHour, RoomType roomType) {
        if(courseRound.getCourseType().isYear()) {
            this.validateYear(courseRound, occurDate);
        }
        
        this.validateStudent(courseRound, occurDate, occurHour);
        
        this.validateCoachTime(courseRound, coach, occurDate, occurHour);
        
        this.validateRoom(courseRound, site, occurDate, occurHour, roomType);
    }

    /*
     * (non-Javadoc)
     * @see co.fitstart.weapp.api.service.CourseAppointService#validateAppointment(co.fitstart.entity.course.round.CourseRound, co.fitstart.entity.site.Site, co.fitstart.entity.principal.Principal, java.util.Date, java.lang.String, co.fitstart.entity.site.RoomType, co.fitstart.entity.course.schedule.CourseSchedule)
     */
    public void validateAppointment(CourseRound courseRound, Site site, Principal coach, Date occurDate,
            String occurHour, RoomType roomType, CourseSchedule courseSchedule) {
        if(courseRound.getCourseType().isYear()) {
            throw new BusinessException("年卡课程不可预约换课");
        }
        
        this.validateStudent(courseRound, occurDate, occurHour, courseSchedule);
        
        this.validateCoachTime(courseRound, coach, occurDate, occurHour, courseSchedule);
        
        this.validateRoom(courseRound, site, occurDate, occurHour, roomType, courseSchedule);
    }

    /**
     * 
     * @param courseRound
     * @param site
     * @param occurDate
     * @param occurHour
     */
    public void validateCourseTime(CourseRound courseRound, Site site, Date occurDate, String occurHour) {
        Date occurTime = DateUtils.parseDate(DateFormatUtils.format(occurDate, "yyyy-MM-dd ") + occurHour + ":00",  "yyyy-MM-dd HH:mm:ss");
        
        if(occurTime.before(new Date())) {
            throw new BusinessException("所选时间小于当前时间，请重新选择");
        }
        
        Date effectiveEndDate  = courseRound.getEffectiveEnd();
        if(effectiveEndDate != null && occurTime.after(org.apache.commons.lang3.time.DateUtils.addMinutes(effectiveEndDate, courseRound.getCourse().isExperience() ? -90 : -60))) {
            throw new BusinessException("所选时间该课程已过期");
        }
        
        Date appointStart = DateUtils.parseDate(DateFormatUtils.format(occurDate, "yyyy-MM-dd ") + site.getAppointStartHour() + ":00", "yyyy-MM-dd HH:mm:ss");
        Date appointEnd = DateUtils.parseDate(DateFormatUtils.format(occurDate, "yyyy-MM-dd ") + site.getAppointEndHour() + ":00", "yyyy-MM-dd HH:mm:ss");
        
        if(StringUtils.isNotEmpty(courseRound.getCourse().getAppointStartTime())) {
            Date courseAppointStartHour = DateUtils.parseDate(DateFormatUtils.format(occurDate, "yyyy-MM-dd ") + courseRound.getCourse().getAppointStartTime() + ":00", "yyyy-MM-dd HH:mm:ss");
            
            if(courseAppointStartHour.after(appointStart)) {
                appointStart = courseAppointStartHour;
            }
        }
        
        if(StringUtils.isNotEmpty(courseRound.getCourse().getAppointEndTime())) {
            Date courseAppointEndHour = DateUtils.parseDate(DateFormatUtils.format(occurDate, "yyyy-MM-dd ") + courseRound.getCourse().getAppointEndTime() + ":00", "yyyy-MM-dd HH:mm:ss");
            if(courseAppointEndHour.before(appointEnd)) {
                appointEnd = courseAppointEndHour;
            }
        }
        
        if(occurTime.before(appointStart) || occurTime.after(org.apache.commons.lang3.time.DateUtils.addMinutes(appointEnd, courseRound.getCourse().isExperience() ? -90 : -60))) {
            throw new BusinessException("所选时间不在该课程可预约时间，请重新选择");
        }
    }

    /**
     * 
     * @param courseRound
     * @param site
     */
    private void validatSite(CourseRound courseRound, Site site) {
        if(courseRound.getCourseType().isYear()) {
            if(courseRound.getSite() != site) {
                throw new BusinessException("年卡课程不可预约其他门店");
            }
        } else {
            if(courseRound.getSite().getArea() != null && courseRound.getSite().getArea().getCityLevel() != null && courseRound.getSite().getArea().getAreaLevel() != null) {
                List<Site> sites = siteDao.listAvailableSite(courseRound.getSite().getArea().getCityLevel(), courseRound.getSite().getArea().getAreaLevel());
                
                if(sites.isEmpty() || !sites.contains(site)) {
                    throw new BusinessException("所选门店不可预约");
                }
            } else {
                if(courseRound.getSite() != site) {
                    throw new BusinessException("所选门店不可预约");
                }
            }
            
        }
    }

    /**
     * 
     * @param courseRound
     * @param occurDate
     */
    private void validateYear(CourseRound courseRound, Date occurDate) {
        //年卡课程一天只能预约一次
        if(courseScheduleDao.count(courseRound, occurDate) > 0) {
            throw new BusinessException("本课程今日已有预约");
        }
        
        //年卡失信用户一周只能预约一次
        if(courseRound.isDishonesty() && courseScheduleDao.count(courseRound, DateUtils.getFirstWeekDay(occurDate), DateUtils.getLastWeekDay(occurDate)) > 0) {
            throw new BusinessException("失信用户，一周只能预约一节课程");
        }
    }

    /**
     * 
     * @param courseRound
     * @param occurDate
     * @param occurHour
     */
    private void validateStudent(CourseRound courseRound, Date occurDate, String occurHour) {
        if(courseScheduleDao.countExperience(courseRound.getStudent(), occurDate, this.getOccurHour(occurHour, -2)) > 0) {
            throw new BusinessException("所选时间您已有其他课程");
        } else {
            List<String> occurHours = new ArrayList<>();
            
            occurHours.add(this.getOccurHour(occurHour, -1));
            occurHours.add(occurHour);
            occurHours.add(this.getOccurHour(occurHour, 1));
            
            if(courseRound.getCourse().isExperience()) {
                occurHours.add(this.getOccurHour(occurHour, 2));
            }
            
            if(courseScheduleDao.count(courseRound.getStudent(), occurDate, occurHours) > 0) {
                throw new BusinessException("所选时间您已有其他课程");
            }
        }
    }

    /**
     * 
     * @param courseRound
     * @param occurDate
     * @param occurHour
     * @param courseSchedule
     */
    private void validateStudent(CourseRound courseRound, Date occurDate, String occurHour, CourseSchedule courseSchedule) {
        if(courseScheduleDao.countExperience(courseRound.getStudent(), occurDate, this.getOccurHour(occurHour, -2), courseSchedule) > 0) {
            throw new BusinessException("所选时间您已有其他课程");
        } else {
            List<String> occurHours = new ArrayList<>();
            
            occurHours.add(this.getOccurHour(occurHour, -1));
            occurHours.add(occurHour);
            occurHours.add(this.getOccurHour(occurHour, 1));
            
            if(courseRound.getCourse().isExperience()) {
                occurHours.add(this.getOccurHour(occurHour, 2));
            }
            
            if(courseScheduleDao.count(courseRound.getStudent(), occurDate, occurHours, courseSchedule) > 0) {
                throw new BusinessException("所选时间您已有其他课程");
            }
        }
    }

    /**
     * 
     * @param courseRound
     * @param coach
     * @param occurDate
     * @param occurHour
     */
    private void validateCoachTime(CourseRound courseRound, Principal coach, Date occurDate, String occurHour) {
        if(courseScheduleDao.countExperience(coach, occurDate, this.getOccurHour(occurHour, -2)) > 0) {
            throw new BusinessException("所选教练已有其他课程");
        } else {
            List<String> occurHours = new ArrayList<>();
            
            occurHours.add(this.getOccurHour(occurHour, -1));
            occurHours.add(occurHour);
            occurHours.add(this.getOccurHour(occurHour, 1));
            
            if(courseRound.getCourse().isExperience()) {
                occurHours.add(this.getOccurHour(occurHour, 2));
            }
            
            if(courseScheduleDao.count(coach, occurDate, occurHours) > 0) {
                throw new BusinessException("所选教练已有其他课程");
            }
        }
        
        List<String> restHours = new ArrayList<>();
        restHours.add(occurHour);
        restHours.add(this.getOccurHour(occurHour, 1));
        
        if(courseRound.getCourse().isExperience()) {
            restHours.add(this.getOccurHour(occurHour, 2));
        }
        
        if(coachDao.count(CoachRestType.DUTY, coach, occurDate, restHours) > 0) {
            throw new BusinessException("所选教练在休息时间");
        }
    }

    private void validateCoachTime(CourseRound courseRound, Principal coach, Date occurDate, String occurHour, CourseSchedule courseSchedule) {
        if(courseScheduleDao.countExperience(coach, occurDate, this.getOccurHour(occurHour, -2), courseSchedule) > 0) {
            throw new BusinessException("所选教练已有其他课程");
        } else {
            List<String> occurHours = new ArrayList<>();
            
            occurHours.add(this.getOccurHour(occurHour, -1));
            occurHours.add(occurHour);
            occurHours.add(this.getOccurHour(occurHour, 1));
            
            if(courseRound.getCourse().isExperience()) {
                occurHours.add(this.getOccurHour(occurHour, 2));
            }
            
            if(courseScheduleDao.count(coach, occurDate, occurHours, courseSchedule) > 0) {
                throw new BusinessException("所选教练已有其他课程");
            }
        }
        
        List<String> restHours = new ArrayList<>();
        restHours.add(occurHour);
        restHours.add(this.getOccurHour(occurHour, 1));
        
        if(courseRound.getCourse().isExperience()) {
            restHours.add(this.getOccurHour(occurHour, 2));
        }
        
        if(courseSchedule.getCourse().isExperience() && DateUtils.isSameDay(occurDate, courseSchedule.getOccurDate())) {
            CoachRestCalendar coachRestCalendar = coachDao.loadCoachRestCalendar(coach, CoachRestType.EXPERIENCE, courseSchedule.getOccurDate(), this.getOccurHour(courseSchedule.getOccurHour(), 2));
            
            if(coachRestCalendar != null && coachDao.count(CoachRestType.DUTY, coach, occurDate, restHours, coachRestCalendar) > 0) {
                throw new BusinessException("所选教练在休息时间");
            } else if(coachRestCalendar == null && coachDao.count(CoachRestType.DUTY, coach, occurDate, restHours) > 0) {
                throw new BusinessException("所选教练在休息时间");
            }
        } else if(coachDao.count(CoachRestType.DUTY, coach, occurDate, restHours) > 0) {
            throw new BusinessException("所选教练在休息时间");
        }
    }

    /**
     * 
     * @param courseRound
     * @param site
     * @param occurDate
     * @param occurHour
     * @param roomType
     */
    public void validateRoom(CourseRound courseRound, Site site, Date occurDate, String occurHour, RoomType roomType) {
        if(site.getLimit(roomType) > 0) {
            if(this.countAvailableRoom(roomType, site, occurDate, occurHour, courseRound.getCourse().isExperience()) <= 0) {
                throw new BusinessException("当前区域已预约满，请选择其他区域");
            }
        } else {
            throw new BusinessException("当前区域已预约满，请选择其他区域");
        }
    }

    /**
     * 
     * @param courseRound
     * @param site
     * @param occurDate
     * @param occurHour
     * @param roomType
     * @param courseSchedule
     */
    public void validateRoom(CourseRound courseRound, Site site, Date occurDate, String occurHour, RoomType roomType, CourseSchedule courseSchedule) {
        if(site.getLimit(roomType) > 0) {
            if(this.countAvailableRoom(roomType, site, occurDate, occurHour, courseRound.getCourse().isExperience(), courseSchedule) <= 0) {
                throw new BusinessException("当前区域已预约满，请选择其他区域");
            }
        } else {
            throw new BusinessException("当前区域已预约满，请选择其他区域");
        }
    }

    /*
     * (non-Javadoc)
     * @see co.fitstart.weapp.api.service.CourseAppointService#validateCancel(co.fitstart.entity.user.Student, co.fitstart.entity.course.schedule.CourseSchedule)
     */
    public void validateCancel(Student student, CourseSchedule courseSchedule) {
        if(student != courseSchedule.getStudent()) {
            throw new BusinessException("课程数据有误");
        }
        
        if(!courseSchedule.couldCancel()) {
            throw new BusinessException("此课时不可取消");
        }
    }

    /*
     * (non-Javadoc)
     * @see co.fitstart.weapp.api.service.CourseAppointService#cancelSchedule(co.fitstart.entity.course.schedule.CourseSchedule)
     */
    public void cancelSchedule(CourseSchedule courseSchedule) {
        Principal coach = courseSchedule.getCoach();
        
        // make message before update
        String message = "您的学员" + courseSchedule.getStudent().getName() + "，有课程取消："
                                  + DateFormatUtils.format(courseSchedule.getOccurDate()) + " " + courseSchedule.getOccurHour() 
                                  + "已被学员取消"
                                  + "，请去系统查看。";
        
        PrincipalMessage principalMessage = new PrincipalMessage(coach);
        genericDaoSupport.save(principalMessage.initialize("课程变更", "您有新的课程变更信息", message));
        
        tencentHandler.sendCorpMessage(message, coach.getMobile());
        
        if(courseSchedule.getCourseType().isYear() || (courseSchedule.getCourseType().isIndividual() && !courseSchedule.getCourse().isExperience())) {
            if(DateUtils.isSameDay(new Date(), courseSchedule.getOccurDate())) {
                CourseScheduleCancelRecord record = courseScheduleDao.loadCourseScheduleCancelRecord(courseSchedule, courseSchedule.getOccurDate());
                
                if(record == null) {
                    record = new CourseScheduleCancelRecord(courseSchedule, true);
                    
                    genericDaoSupport.save(record);
                }
            }
        }
        
        if(courseSchedule.getCourseType().isIndividual()) {
            if(courseSchedule.getCourse().isExperience()) {
                CoachRestCalendar experience = coachDao.loadCoachRestCalendar(coach, CoachRestType.EXPERIENCE, courseSchedule.getOccurDate(), this.getOccurHour(courseSchedule.getOccurHour(), -2));
                if(experience != null) {
                    genericDaoSupport.update(experience.disable());
                }
            }
            courseSchedule.cancel();
        } else if (courseSchedule.getCourseType().isYear()) {
            courseSchedule.disabled();
        }
        genericDaoSupport.update(courseSchedule);
        
        RoomScheduleRecord roomScheduleRecord = courseSchedule.getRoomSchedule();
        if(roomScheduleRecord != null) {
            genericDaoSupport.update(roomScheduleRecord.disabled());
        }
        
        courseScheduleDao.disableScheduleSmsNotice(courseSchedule);
        
        this.recalculateStudentCourseware(courseSchedule.getCourseRound());
    }

    /**
     * 重新计算学员课件完成情况
     * @param courseRound
     */
    private void recalculateStudentCourseware(CourseRound courseRound) {
        if(courseRound.getCourseType().isYear() || (courseRound.getCourseType().isIndividual() && !courseRound.getCourse().isExperience())) {
            if(courseRound.getCourseStatus().isReady() || courseRound.getCourseStatus().isHold()
                    || courseRound.getCourseStatus().isExpire() || courseRound.getCourseStatus().isActivate()) {
                //重新计算训练部位完成情况
                CourseSchedule courseSchedule1 = courseScheduleDao.load(courseRound);
                if(courseSchedule1 == null) {
                    genericDaoSupport.update(courseRound.editPlannedContent(0));
                } else {
                    genericDaoSupport.update(courseRound.editPlannedContent(courseScheduleDao.count(courseRound, courseSchedule1.getOccurDate(), courseSchedule1.getOccurHour())));
                }
                
                //重新计算上课课件完成情况
                CourseSchedule courseSchedule2 = coursewareDao.loadCourseSchedue(courseRound);
                if(courseSchedule2 == null) {
                    genericDaoSupport.update(courseRound.editPlannedCourseware(0));
                } else {
                    genericDaoSupport.update(courseRound.editPlannedCourseware(courseScheduleDao.count(courseRound, courseSchedule2.getOccurDate(), courseSchedule2.getOccurHour())));
                }
            }
        }
    }
}
